<?php
/**
 * WpConnectBase.php
 * 
 * This file contains the abstract WpConnectBase class, the base class for all connector implementations. It includes 
 * dbms independent functions such as CRUD query creators and connection configuration.
 * 
 * @author Robert Schmidt <robert@wildpack.de>
 * @version 1.0
 * @package wildpack
 * @subpackage dataaccess.wpconnect
 */


/**
 * The IWpConnect Interface to implement
 */
require_once(dirname(__FILE__).'/IWpConnect.php');

/**
 * A class which contains all error messages mapped on numbers
 */
require_once(dirname(__FILE__).'/WpConnectErrors.php');

if (!interface_exists('IWpConnect')) {
	die('Fatal error: You must include the IWPConnect interface in order to use the WpConnectBase class! Script stopped!');
}
if (!class_exists('WpObservable')) {
	die('Fatal error: You must include the observe package in order to use the connector package. Script stopped!');
}
if (!class_exists('WpConnectErrors')) {
	die('Fatal error: You must include the WPConnectErrors class in order to use the connector package. Script stopped!');
}

/**
 * The base WpConnect class
 * 
 * The base class for all connector implementations. It includes dbms independent functions such as CRUD query creators and connection configuration.
 * 
 * <code>
 * $conn = new WpConnectXY(array(
 * 		'location' => 'host/datasource',
 * 		'user' => 'user',
 * 		'password' => 'password')
 * );
 * 
 * $conn->select('*', 'table', 'WHERE id = 1);
 * $arr = $conn->getResult('arrays');
 * </code>
 * 
 * @author Robert Schmidt <robert@wildpack.de>
 * @version 1.0
 * @package wildpack
 * @subpackage dataaccess.wpconnect
 * @event error: handler(connector, location, title, body, number)
 * @event postDelete: handler(connector, table, condition, count)
 * @event postInsert: handler(connector, table, data)
 * @event postSelect: handler(connector, table, fields, condition)
 * @event postUpdate: handler(connector, table, condition, data, count)
 * @event preDelete: handler(connector, table, condition)
 * @event preInsert: handler(connector, table, data)
 * @event preSelect: handler(connector, table, fields, condition)
 * @event preUpdate: handler(connector, table, condition, data)
 */
abstract class WpConnectBase extends WpConnectErrors implements IWpConnect 
{
	/**
   * Determines wether a multirequest has been started or not
   */
	private $blnMrStarted = false;
	
  /**
   * The array of requests added to a multi request
   */	
	protected $arrMr = array();
  
  /**
   * The multi request flags, a string combination of [insert], [update] and [delete]
   */
	protected $strMrFlags = '';
  
  /**
   * The total number of loaded datasets
   */
	protected $Total = 0;
  
  /**
   * This is where the raw result is stored
   */
	protected $RawResult = null;
	
	/**
	 * @var The location to connect to. When using databases this string contains the 
	 * host, port and database like: "localhost:1234/mydb" or "localhost/mydb"
	 */
	public $Location = '';
	
	/**
	 * @var The name of the user, used to connect to database
	 */
	public $User = '';
	
	/**
	 * @var The password of the user, used to connect to database
	 */
	public $Password = '';
	
	/**
   * Either the log object or a an array of log entries
   */
	public $Log = null;
	
	/**
	 * Converts the result into an array of instances of stdClass 
	 * @return array An array of stdClass instances or null if no result is available
	 */
	protected abstract function convertToObjects();
	
	/**
	 * Converts the result into an array of arrays 
	 * @return array An array of arrays or null if no result is available
	 */
	protected abstract function convertToArrays();
	
	/**
	 * Converts the result into an array of associative arrays 
	 * @return array An array of associative arrays or null if no result is available
	 */
	protected abstract function convertToAssocs();
	
	/**
	 * Converts the raw data using a custom algorithm
	 * @param object $custom A custom class instance to use for converting
	 */
	protected abstract function convertToCustom(&$custom);
	
	/** 
	 * Writes an entry to the log in filesystem
	 * @param string $location The location of the error, message, action which causes the log
	 * @param string $user The currently logged-in user (or '' for Anonymous)
	 * @param string $title The title of the log entry
	 * @param string $body The body of the log entry
	 * @param string $number The error/action number to identify the log entry
	 * @param string $type The log type (error, action, message)
	 * @param array $params An array of params to assign to the log body
	 */
	protected function writeToLog($location, $user, $title, $body, $number, $type='error', $params=null)
	{
		if ($user === '') {
			$user = 'Anonymous';
		}
		if ($type === '') {
			$type = 'error';
		}
		
		//-- Get body and title --
		if (array_key_exists($number, $this->ErrorTemplates)) {
			$title = $this->ErrorTemplates[$number][0];
			$body = $this->ErrorTemplates[$number][1];
		}
		
		//-- Assign params --
		if (is_array($params)) {
			for ($i=0; $i < count($params); $i++) {
				$pS = '[param'.strval($i).']';
				$body = str_replace($pS, $params[$i], $body);
			}
		}
		
		if (!is_null($this->Log) && !is_array($this->Log)) {
			switch ($type) {
				case 'error' : $this->Log->logError($location, $user, $title, $body, $number, 1); break;
				case 'action' : $this->Log->logAction($location, $user, $title, $body, $number, 3); break;
				default :$this->Log->logMessage($location, $user, $title, $body, $number, 3); break;
			}
      if ($type === 'error') {
        $this->fireEvent('error', array($this, $location, $title, $body, $number));
      }
			return;
		}
		
		$strLogString = 'Datetime: '.date('c')."\t".
			'Location: '.$location."\t".
			'User: '.$user."\t".
			'Title: '.$title."\t".
			'Body: '.$body."\t".
			'ErrNo: '.$number;
		
		$this->Log[] = $strLogString;
    if ($type === 'error') {
      $this->fireEvent('error', array($this, $location, $title, $body, $number));
    }
	}
	
	/**
   * The class constructor
   * @param object $config[optional] The configuration object
   * @cfg string location The location of the datasource to use @see setLocation
   * @cfg string user The user to use for datasource authentication
   * @cfg string password The password to use for authentication
   * @cfg object log A log object to log errors to (must implement the interface IWPLog).
   * If no log object given, the errors are logged to an array which can be retrieved by getLog
   */
	public function __construct($config = null)
	{
		if (is_null($config) === false) {
			if (isset($config['location'])) {
				$this->Location  = $config['location'];
			}
			if (isset($config['user'])) {
				$this->User = $config['user'];
			}
			if (isset($config['password'])) {
				$this->Password  = $config['password'];
			}
			if (isset($config['log'])) {
				$objLog = $config['log'];
				$arrWPConnectInterfaces = class_implements($objLog);
				if (in_array('IWpLog', $arrWPConnectInterfaces) === true) {
					$this->Log = $objLog;
				}
			} else {
				$this->Log = array();
			}
		}
		
		$this->addEvents(array('error','preInsert', 'postInsert', 'preUpdate', 'postUpdate', 
			'preDelete', 'postDelete', 'preSelect', 'postSelect'));
	}
	
	/**
   * Counts the requests collected for a multi-request
   * @return The number of request in the current multi request
   */
	public function countMultiRequests()
	{
		if ($this->blnMrStarted !== true) {
			return 0;
		}
		
		if (is_array($this->arrMr) !== true) {
			return 0;
		}
		
		return count($this->arrMr);
	}
	
	/**
   * Retrieves the result of the last query or command
   * @param string $type The type of the return value (objects, arrays, assocs, custom)
   * @param object $custom An object which is used to retrieve the data customized
   * @return mixed Returns the converted result
   */
	public function getResult($type='objects', &$custom=null)
	{
		switch ($type) {
			case 'custom' : return $this->convertToCustom($custom); break;
			case 'assocs' : return $this->convertToAssocs(); break;
			case 'arrays' : return $this->convertToArrays(); break;
			default: return $this->convertToObjects(); break;
		}
	}
	
	/** 
   * Retrieves the total number of datasets (not the number, limited by LIMIT)
   * @return int The number of datasets returned or affected by a query
   */
	public function getTotal()
	{
		return $this->Total;
	}
		
	/**
   * Determines wether a multirequest was started or not
   * @return True if multirequest was started, false else
   * @see startMultiRequest()
   * @see stopMultiRequest()
   */
	public function isMultiRequest()
	{
		return $this->blnMrStarted;
	}
	
	/**
	 * Fires if an error occurs
	 * @param object $object The object storing the callback function or null if global function
	 * @param string $function The name of the callback function
	 * @callbackparam object $connector The datasource connector which fired the event
	 * @callbackparam string $location The error location
	 * @callbackparam string $title The error title
	 * @callbackparam string $body The error body
	 * @callbackparam string $number The error number
	 * @event error
	 */
	public function onError($object, $function)
	{
		$this->on('error', $object, $function);
	}
		
	/**
	 * Fires after a dataset was deleted or deletion request stored for multi request
	 * @param object $object The object storing the callback function or null if global function
	 * @param string $function The name of the callback function
	 * @callbackparam object $connector The datasource connector which fired the event
	 * @callbackparam string $table The table to delete datasets of
	 * @callbackparam string $condition The condition to use for deletion
	 * @callbackparam int $total The number of deleted datasets
	 * @event postDelete
	 */
	public function onPostDelete($object, $function)
	{
		$this->on('postDelete', $object, $function);
	}
	
	/**
	 * Fires after a dataset was inserted or stored for multi request
	 * @param object $object The object storing the callback function or null if global function
	 * @param string $function The name of the callback function
	 * @callbackparam object $connector The datasource connector which fired the event
	 * @callbackparam string $table The used table
	 * @callbackparam string $condition The used condition
	 * @event postInsert
	 */
	public function onPostInsert($object, $function)
	{
		$this->on('postInsert', $object, $function);
	}
	
	/**
	 * Fires after datasets were selected
	 * @param object $object The object storing the callback function or null if global function
	 * @param string $function The name of the callback function
	 * @callbackparam object $connector The datasource Prozentconnector which fired the event
	 * @callbackparam string $table The used table
   * @callbackparam string $fields The selected fields
	 * @callbackparam string $condition The used condition
	 * @event postSelect
	 */
	public function onPostSelect($object, $function)
	{
		$this->on('postSelect', $object, $function);
	}
	
	/**
	 * Fires after a dataset was updated or update query was stored for multi request
	 * @param object $object The object storing the callback function or null if global function
	 * @param string $function The name of the callback function
	 * @callbackparam object $connector The datasource connector which fired the event
	 * @callbackparam string $table The used table
	 * @callbackparam string $condition The used condition
	 * @callbackparam array $update The data array used for update
	 * @callbackparam int $total The number of updated datasets
	 * @event postUpdate
	 */
	public function onPostUpdate($object, $function)
	{
		$this->on('postUpdate', $object, $function);
	}
	
	/**
	 * Fires before a dataset is deleted or deletion request will be stored for multi request
	 * @param object $object The object storing the callback function or null if global function
	 * @param string $function The name of the callback function
	 * @callbackparam object $connector The datasource connector which fired the event
	 * @callbackparam string $table The used table
	 * @callbackparam string $condition The used condition
	 * @event preDelete
	 */
	public function onPreDelete($object, $function)
	{
		$this->on('preDelete', $object, $function);
	}
	
	/**
	 * Fires before a dataset will be inserted or stored for multi request
	 * @param object $object The object storing the callback function or null if global function
	 * @param string $function The name of the callback function
	 * @callbackparam object $connector The datasource connector which fired the event
	 * @callbackparam string $table The used table
	 * @callbackparam array $insertion The insertion array containing all fields and values to insert
	 * @event preInsert
	 */
	public function onPreInsert($object, $function)
	{
		$this->on('preInsert', $object, $function);
	}
	
	/**
	 * Fires before datasets were selected
	 * @param object $object The object storing the callback function or null if global function
	 * @param string $function The name of the callback function
	 * @callbackparam object $connector The datasource connector which fired the event
	 * @callbackparam string $table The used table
   * @callbackparam string $fields The fields to select
	 * @callbackparam string $condition The used condition
	 * @event preSelect
	 */
	public function onPreSelect($object, $function)
	{
		$this->on('preSelect', $object, $function);
	}
	
	/**
	 * Fires before a dataset will be updated or update query will be stored for multi request
	 * @param object $object The object storing the callback function or null if global function
	 * @param string $function The name of the callback function
	 * @callbackparam object $connector The datasource connector which fired the event
	 * @callbackparam string $table The used table
	 * @callbackparam string $condition The used condition
   * @callbackparam array $update The data table to use for the update
	 * @event preUpdate
	 */
	public function onPreUpdate($object, $function)
	{
		$this->on('preUpdate', $object, $function);
	}
	
	/**
	 * Reset the connector for a new query
	 */
	public function reset()
	{
		$this->Total = 0;
		$this->RawResult = null;
	}
	
	/**
   * Starts a multi request to collect requests and fire only one transaction
   * @param bool $insert[optional] True, to collect insert requests for transaction, false else
   * @param bool $update[optional] True, to collect update requests for transaction, false else
   * @param bool $delete[optional] True, to collect delete requests for transaction, false else
   */
	public function startMultiRequest($insert=true, $update=true, $delete=true)
	{
		$this->blnMrStarted = true;
		unset($this->arrMr);
		$this->arrMr = array();
		$this->strMrFlags = '';
		$this->strMrFlags .= $insert === true ? '[insert]' : '';
		$this->strMrFlags .= $update === true ? '[update]' : '';
		$this->strMrFlags .= $delete === true ? '[delete]' : '';
	}
	
	/**
   * Stops collecting requests for multi request, and optionally starts the mutli request
   * @return The number of requests processed
   * @param bool $process[optional] True, to start processing the multi request, false else
   */
	public function stopMultiRequest($process=false)
	{
		$loc = basename(__FILE__).'/'.__METHOD__;
		$intReturn = 0;
		
		if ($process === true) {
			$intReturn = $this->processMultiRequest();
			if ($intReturn === false) {
				$this->writeToLog($loc, '', '', '', '90101', '', array('processMultiRequest'));
			}
		}
				
		//-- Reset mr --
		$this->blnMrStarted = false;
		unset($this->arrMr);
		$this->arrMr = array();
		$this->strMrFlags = '';
		
		return $intReturn;
	}
}

/* End of file WpConnectBase.php */
/* Location: ./wildpack/dataaccess/wpconnect/WpConnectBase.php */
